实现一个类型判断函数

  1. 判断 null
  2. 判断基础类型
  3. 使用Object.prototype.toString.call(target)来判断引用类型
/**
 * 类型判断
 */
function getType(target) {
  //先处理最特殊的 Null
  if (target === null) {
    return "null";
  }
  //判断是不是基础类型
  const typeOfT = typeof target;
  if (typeOfT !== "object") {
    return typeOfT;
  }
  //肯定是引用类型了
  const template = {
    "[object Object]": "object",
    "[object Array]": "array",
    "[object Function]": "function",
    // 一些包装类型
    "[object String]": "object - string",
    "[object Number]": "object - number",
    "[object Boolean]": "object - boolean",
  };
  const typeStr = Object.prototype.toString.call(target);
  return template[typeStr];
}

强制类型转换

1 + "1"; // '11'
2 * "2"; // 4
[(1, 2)] + [2, 1]; // '1,22,1'
// + "b" -> NaN
"a" + +"b"; // "aNaN"

转 Boolean

以下都为假值,其他所有值都转为 true,包括所有对象(空对象,空数组也转为真)。

  • false
  • undefined
  • null
  • ''
  • NaN
  • 0
  • -0

对象转基本类型

对象在转换基本类型时,会调用valueOf, 需要转成字符类型时调用toString

var a = {
  valueOf() {
    return 0;
  },
  toString() {
    return "1";
  },
};

1 + a; // 1
"1".concat(a); //"11"

也可以重写 Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高Symbol.toPrimitive 指将被调用的指定函数值的属性转换为相对应的原始值。

类型转换

运算中其中一方为字符串,那么就会把另一方也转换为字符串.如果一方不是字符串或者数字,那么会将它转换为数字或者字符串

1 + "1"; // '11'
true + true; // 2
4 + [1, 2, 3]; // "41,2,3"

还需要注意这个表达式'a' + + 'b'

"a" + +"b"; // -> "aNaN"

因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型。

== 操作符

对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换

判断流程:

  1. 首先会判断两者类型是否相同。相同的话就是比大小了
  2. 类型不相同的话,那么就会进行类型转换
  3. 会先判断是否在对比 null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
1 == '1'1 ==  1
  1. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
'1' == true'1' ==  11  ==  1
  1. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断
'1' == { a: 'b' }'1' == '[object Object]'
  1. 两边都是对象的话,那么只要不是同一对象的不同引用,都为 false

注意,只要出现 NaN,就一定是 false,因为就连 NaN 自己都不等于 NaN 对于 NaN,判断的方法是使用全局函数 isNaN()

=== 操作符

不转类型,直接判断类型和值是否相同。 但是 NaN === NaN 还是 false

{} 等于 true 还是 false

var a = {};

a == true; // -> ?
a == false; // -> ?

答案是两个都为 false 因为 a.toString() -> '[object Object]' -> NaN

1 与 Number(1)有什么区别

var a = Number(1); // 1
var b = new Number(1); // Number {[[PrimitiveValue]]: 1}
typeof a; // number
typeof b; // object
a == b; // true
  • var a = 1 是一个常量,而 Number(1)是一个函数
  • new Number(1)返回的是一个对象
  • a==b 为 true 是因为所以在求值过程中,总是会强制转为原始数据类型而非对象,例如下面的代码:
typeof 123; // "number"
typeof new Number(123); // "object"
123 instanceof
  Number(
    // false
    new Number(123)
  ) instanceof
  Number; // true
123 === new Number(123); // false

console.log(!!(new Boolean(false))输出什么 [易混淆]

true 布尔的包装对象 Boolean 的对象实例,对象只有在 null 与 undefined 时,才会认定为布尔的 false 值,布尔包装对象本身是个对象,对象->布尔 都是 true,所以 new Boolean(false)其实是布尔的 true,看下面这段代码:

if (new Boolean(false)) {
  alert("true!!");
}

只有使用了 valueOf 后才是真正的转换布尔值,与上面包装对象与原始资料转换说明的相同:

!!new Boolean(false)(
  //true
  new Boolean(false)
).valueOf(); //false

obj.toString() 和 Object.prototype.toString.call(obj)

同样是检测对象 obj 调用 toString 方法,obj.toString()的结果和 Object.prototype.toString.call(obj)的结果不一样,这是为什么?

这是因为 toString 为 Object 的原型方法,而 Array ,function 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法(function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串.....),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型),所以采用 obj.toString()不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 上原型 toString 方法。 如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)。这样我们就可以获得类似 [object Type] 的字符串。

let a;
// 我们也可以这样判断 undefined
a === undefined;
// 但是 undefined 不是保留字,能够在低版本浏览器被赋值
let undefined = 1;
// 这样判断就会出错
// 所以可以用下面的方式来判断,并且代码量更少
// 因为 void 后面随便跟上一个组成表达式
// 返回就是 undefined
a === void 0;

类型转换

对象转基本类型

对象在转换基本类型时,首先会调用 valueOf 然后调用 toString。并且这两个方法你是可以重写的。

let a = {
  valueOf() {
    return 0;
  },
};

当然你也可以重写 Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高。

let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return "1";
  },
  [Symbol.toPrimitive]() {
    return 2;
  },
};
1 + a; // => 3
"1" + a; // => '12'

四则运算符

只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。

1 + "1"; // '11'
2 * "2"[(1, 2)] + // 4
  [2, 1]; // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'

对于加号需要注意这个表达式 'a' + + 'b'

"a" + +"b"; // -> "aNaN"
// 因为 + 'b' -> NaN
// 你也许在一些代码中看到过 + '1' -> 1

== 操作符

上图中的 toPrimitive 就是对象转基本类型。

这里来解析一道题目 [] == ![] // -> true ,下面是这个表达式为何为 true 的步骤

// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true

比较运算符

  1. 如果是对象,就通过 toPrimitive 转换对象
  2. 如果是字符串,就通过 unicode 字符索引来比较

nullundefined和未声明变量之间有什么区别?如何检查判断这些状态值?

当你没有提前使用varletconst声明变量,就为一个变量赋值时,该变量是未声明变量(undeclared variables)。未声明变量会脱离当前作用域,成为全局作用域下定义的变量。在严格模式下,给未声明的变量赋值,会抛出ReferenceError错误。和使用全局变量一样,使用未声明变量也是非常不好的做法,应当尽可能避免。要检查判断它们,需要将用到它们的代码放在try/catch语句中。

function foo() {
  x = 1; // 在严格模式下,抛出 ReferenceError 错误
}

foo();
console.log(x); // 1

当一个变量已经声明,但没有赋值时,该变量的值是undefined。如果一个函数的执行结果被赋值给一个变量,但是这个函数却没有返回任何值,那么该变量的值是undefined。要检查它,需要使用严格相等(===);或者使用typeof,它会返回'undefined'字符串。请注意,不能使用非严格相等(==)来检查,因为如果变量值为null,使用非严格相等也会返回true

var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === "undefined"); // true

console.log(foo == null); // true. 错误,不要使用非严格相等!

function bar() {}
var baz = bar();
console.log(baz); // undefined

null只能被显式赋值给变量。它表示空值,与被显式赋值 undefined 的意义不同。要检查判断null值,需要使用严格相等运算符。请注意,和前面一样,不能使用非严格相等(==)来检查,因为如果变量值为undefined,使用非严格相等也会返回true

var foo = null;
console.log(foo === null); // true

console.log(foo == undefined); // true. 错误,不要使用非严格相等!

作为一种个人习惯,我从不使用未声明变量。如果定义了暂时没有用到的变量,我会在声明后明确地给它们赋值为null

如何进行 js 的类型判断

Object.prototype.toString

var number = 1; // [object Number]
var string = "123"; // [object String]
var boolean = true; // [object Boolean]
var und = undefined; // [object Undefined]
var nul = null; // [object Null]
var obj = { a: 1 }; // [object Object]
var array = [1, 2, 3]; // [object Array]
var date = new Date(); // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g; // [object RegExp]
var func = function a() {}; // [object Function]
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]
function a() {
  console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();

推荐使用该方法来判断 使用该方法我们可以封装一个 isType 方法来对类型进行判断

let isType = (type) => (obj) => {
  return Object.prototype.toString.call(obj) === "[object " + type + "]";
};

或者

var type = function (o) {
  var s = Object.prototype.toString.call(o);
  return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};

[
  "Null",
  "Undefined",
  "Object",
  "Array",
  "String",
  "Number",
  "Boolean",
  "Function",
  "RegExp",
].forEach(function (t) {
  type["is" + t] = function (o) {
    return type(o) === t.toLowerCase();
  };
});

type.isObject({}); // true
type.isNumber(NaN); // true
type.isRegExp(/abc/); // true

constructor

constructor 也不是保险的,因为 constructor 属性是可以被修改的,会导致检测出的结果不正确

console.log([].constructor === Array)   // true
function a() {}
console.log(a.constructor === Function)   // true
console.log(12.constructor === Number)  // true
console.log('22'.constructor === String)  // true
console.log([] .constructor ===  Array)   // true
console.log({a: 1}.constructor ===  Object) // true
console.log(true.constructor === Boolean) // true
console.log(json.constructor === Object) // true
console.log((new Date()).constructor === Date)   // true
console.log(reg.constructor ===  RegExp) //true
console.log(error.constructor === Error) // true

类型转换题

typeof NaN; // 'number'
0.1 + 0.5 == 0.6 // true
0.1 + 0.2 == 0.3 // false

[] + [] // ''
true + true + true === 3 // true
true - true === 0 // true
[] == 0 // true
[] == false // true
// [] => '' => 0
{} == false // false
// {} => '[object Object]' => NaN
[1] == [1] // false
{} == {} // false
Last Updated:
Contributors: yiliang114